#include "drake/multibody/parsing/detail_sdf_parser.h"

#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>

#include <sdf/Error.hh>
#include <sdf/Frame.hh>
#include <sdf/Joint.hh>
#include <sdf/JointAxis.hh>
#include <sdf/Link.hh>
#include <sdf/Model.hh>
#include <sdf/ParserConfig.hh>
#include <sdf/Root.hh>
#include <sdf/World.hh>

#include "drake/geometry/geometry_instance.h"
#include "drake/math/rigid_transform.h"
#include "drake/math/rotation_matrix.h"
#include "drake/multibody/parsing/detail_ignition.h"
#include "drake/multibody/parsing/detail_make_model_name.h"
#include "drake/multibody/parsing/detail_path_utils.h"
#include "drake/multibody/parsing/detail_sdf_diagnostic.h"
#include "drake/multibody/parsing/detail_sdf_geometry.h"
#include "drake/multibody/parsing/detail_urdf_parser.h"
#include "drake/multibody/parsing/scoped_names.h"
#include "drake/multibody/tree/ball_rpy_joint.h"
#include "drake/multibody/tree/fixed_offset_frame.h"
#include "drake/multibody/tree/planar_joint.h"
#include "drake/multibody/tree/prismatic_joint.h"
#include "drake/multibody/tree/prismatic_spring.h"
#include "drake/multibody/tree/revolute_joint.h"
#include "drake/multibody/tree/revolute_spring.h"
#include "drake/multibody/tree/scoped_name.h"
#include "drake/multibody/tree/screw_joint.h"
#include "drake/multibody/tree/spatial_inertia.h"
#include "drake/multibody/tree/uniform_gravity_field_element.h"
#include "drake/multibody/tree/universal_joint.h"
#include "drake/multibody/tree/weld_joint.h"

namespace drake {
namespace multibody {
namespace internal {

using drake::internal::DiagnosticDetail;
using drake::internal::DiagnosticPolicy;
using Eigen::Matrix3d;
using Eigen::Translation3d;
using Eigen::Vector3d;
using geometry::GeometryInstance;
using math::RigidTransformd;
using math::RotationMatrixd;
using std::unique_ptr;

// Unnamed namespace for free functions local to this file.
namespace {

// A short summary of parsing stages in libsdformat:
//
// When we load a model using `sdf::Root::Load`, the following happens:
// * libsdformat parses the XML into `sdf::ElementPtr` objects. Included
// SDFormat models are expanded during this process. If the included model is
// not an SDFormat file type and there are custom parsers registered in the
// passed in `ParserConfig`, the contents of the `<include>` tag are saved for
// later processing
// * DOM objects are created from `sdf::ElementPtr` objects. During the creation
// of `sdf::World` and `sdf::Model`, custom parsers are called on the
// non-SDFormat included files mentioned above. This is what's called the
// Interface API.
// * Drake's custom parser (eg. detail_urdf_parser) parses the files into a
// MultibodyPlant.
// * The frame bearing elements (Links, Joints, Frames, and Models) are read
// back from the MultibodyPlant and returned to libsdformat via the Interface
// API data structures.
// * Once DOM object creation is complete, libsdformat constructs the frame and
// pose graph for the entire model.
// * `sdf::Root::Load` returns and Drake starts its parsing stage where it
// populates the MultibodyPlant using the DOM objects created above (e.g.
// AddModelsFromSpecification).

// `ModelInstanceIndexRange` is a data structure to hold model instances that
// were created during libsdformat's Interface API callback for handling merged
// models. When handling merged models during libsdformat's Interface API
// callback, we have to create model instances of parents of merged models since
// the parent models are regular SDFormat elements and will only be handled at a
// later stage of parsing. This data structure is used to store these parent
// model instances so that when we're going through Drake's parsing stage (last
// step in the parsing stage summary above), we are aware of these model
// instances and not try to recreate them.
using ModelInstanceIndexRange =
    std::pair<ModelInstanceIndex, ModelInstanceIndex>;

// Returns the model instance name for the given `instance`, unless it's the
// world model instance in which case returns the empty string.
std::string GetInstanceScopeNameIgnoringWorld(
    const MultibodyPlant<double>& plant,
    ModelInstanceIndex instance) {
  if (instance != plant.world_body().model_instance()) {
    return plant.GetModelInstanceName(instance);
  } else {
    return "";
  }
}

// Given a @p relative_nested_name to an object, this function returns the model
// instance that is an immediate parent of the object and the local name of the
// object. The local name of the object does not have any scoping delimiters.
//
// @param[in] relative_nested_name
//   The name of the object in the scope of the model associated with the given
//   @p model_instance. The relative nested name can contain the scope delimiter
//   to reference objects nested in child models.
// @param[in] model_instance
//   The model instance in whose scope @p relative_nested_name is defined.
// @param[in] plant
//   The MultibodyPlant object that contains the model instance identified by @p
//   model_instance.
// @returns A pair containing the resolved model instance and the local name of
// the object referenced by @p relative_nested_name.
std::pair<ModelInstanceIndex, std::string> GetResolvedModelInstanceAndLocalName(
    const std::string& relative_nested_name, ModelInstanceIndex model_instance,
    const MultibodyPlant<double>& plant) {
  auto [parent_name, unscoped_local_name] =
      sdf::SplitName(relative_nested_name);
  ModelInstanceIndex resolved_model_instance = model_instance;

  if (!parent_name.empty()) {
    // If the parent is in the world_model_instance, the name can be looked up
    // from the plant without creating an absolute name.
    if (world_model_instance() == model_instance) {
      resolved_model_instance = plant.GetModelInstanceByName(parent_name);
    } else {
      const std::string parent_model_absolute_name = sdf::JoinName(
          plant.GetModelInstanceName(model_instance), parent_name);

      resolved_model_instance =
        plant.GetModelInstanceByName(parent_model_absolute_name);
    }
  }

  return {resolved_model_instance, unscoped_local_name};
}
// Returns true if `str` starts with `prefix`. The length of `prefix` has to be
// strictly less than the size of `str`.
bool StartsWith(const std::string_view str, const std::string_view prefix) {
  return prefix.size() < str.size() &&
         std::equal(str.begin(), str.begin() + prefix.size(), prefix.begin());
}

// Calculates the scoped name of a body relative to the model instance @p
// relative_to_model_instance. If the body is a direct child of the model,
// this simply returns the local name of the body. However, if the body is
// a child of a nested model, the local name of the body is prefixed with the
// scoped name of the nested model. If the relative_to_model_instance is the
// world_model_instance, the local name of the body is prefixed with the model
// name.
std::string GetRelativeBodyName(
    const Body<double>& body,
    ModelInstanceIndex relative_to_model_instance,
    const MultibodyPlant<double>& plant) {
  const std::string& relative_to_model_absolute_name =
      plant.GetModelInstanceName(relative_to_model_instance);
  // If the relative_to_model instance is the world_model_instance, we need to
  // prefix the body name with the model name
  if (relative_to_model_instance == world_model_instance()) {
    const std::string& model_absolute_name =
        plant.GetModelInstanceName(body.model_instance());

    return sdf::JoinName(model_absolute_name, body.name());
  } else if (body.model_instance() != relative_to_model_instance) {
    // If the body is inside a nested model, we need to prefix the
    // name with the relative name of the nested model.
    const std::string& nested_model_absolute_name =
        plant.GetModelInstanceName(body.model_instance());
    // The relative_to_model_absolute_name must be a prefix of the
    // nested_model_absolute_name. Otherwise the nested model is not a
    // descendent of the model relative to which we are computing the name.
    const std::string required_prefix =
        relative_to_model_absolute_name + std::string(sdf::kScopeDelimiter);
    DRAKE_DEMAND(StartsWith(nested_model_absolute_name, required_prefix));

    const std::string nested_model_relative_name =
        nested_model_absolute_name.substr(required_prefix.size());

    return sdf::JoinName(nested_model_relative_name, body.name());
  } else {
    return body.name();
  }
}

// This takes an `sdf::SemanticPose`, which defines a pose relative to a frame,
// and resolves its value with respect to another frame.
math::RigidTransformd ResolveRigidTransform(
    const SDFormatDiagnostic& diagnostic,
    const sdf::SemanticPose& semantic_pose,
    const std::string& relative_to = "") {
  gz::math::Pose3d pose;
  sdf::Errors errors = semantic_pose.Resolve(pose, relative_to);
  diagnostic.PropagateErrors(errors);
  return ToRigidTransform(pose);
}

Eigen::Vector3d ResolveAxisXyz(
    const SDFormatDiagnostic& diagnostic,
    const sdf::JointAxis& axis) {
  gz::math::Vector3d xyz;
  sdf::Errors errors = axis.ResolveXyz(xyz);
  diagnostic.PropagateErrors(errors);
  return ToVector3(xyz);
}

std::string ResolveJointParentLinkName(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint) {
  std::string link;
  sdf::Errors errors = joint.ResolveParentLink(link);
  diagnostic.PropagateErrors(errors);
  return link;
}

std::string ResolveJointChildLinkName(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint) {
  std::string link;
  sdf::Errors errors = joint.ResolveChildLink(link);
  diagnostic.PropagateErrors(errors);
  return link;
}

// Helper method to extract the SpatialInertia M_BBo_B of body B, about its body
// frame origin Bo and, expressed in body frame B, from a gz::Inertial
// object.
SpatialInertia<double> ExtractSpatialInertiaAboutBoExpressedInB(
    const SDFormatDiagnostic& diagnostic,
    const sdf::ElementPtr link_element,
    const gz::math::Inertiald& Inertial_BBcm_Bi) {
  double mass = Inertial_BBcm_Bi.MassMatrix().Mass();

  // Pose of the "<inertial>" frame Bi in the body frame B.
  // TODO(amcastro-tri): Verify we don't get funny results when X_BBi is not
  // the identity matrix.
  // TODO(amcastro-tri): SDF seems to provide <inertial><frame/></inertial>
  // together with <inertial><pose/></inertial>. It'd seem then the frame B
  // in X_BI could be another frame. We rely on Inertial::Pose() to ALWAYS
  // give us X_BI. Verify this.
  const RigidTransformd X_BBi = ToRigidTransform(Inertial_BBcm_Bi.Pose());

  // TODO(amcastro-tri): Verify that gz::math::Inertial::MOI() ALWAYS is
  // expresed in the body frame B, regardless of how a user might have
  // specified frames in the sdf file. That is, that it always returns R_BBcm_B.
  // TODO(amcastro-tri): Verify that gz::math::Inertial::MassMatrix()
  // ALWAYS is in the inertial frame Bi, regardless of how a user might have
  // specified frames in the sdf file. That is, that it always returns
  // M_BBcm_Bi.
  const gz::math::Matrix3d I = Inertial_BBcm_Bi.MassMatrix().Moi();
  return ParseSpatialInertia(diagnostic.MakePolicyForNode(*link_element),
                             X_BBi, mass,
                             {.ixx = I(0, 0), .iyy = I(1, 1), .izz = I(2, 2),
                              .ixy = I(1, 0), .ixz = I(2, 0), .iyz = I(2, 1)});
}

// Helper method to retrieve a Body given the name of the link specification.
const Body<double>& GetBodyByLinkSpecificationName(
    const std::string& link_name,
    ModelInstanceIndex model_instance, const MultibodyPlant<double>& plant) {
  // SDF's convention to indicate a joint is connected to the world is to either
  // name the corresponding link "world" or just leave it unnamed.
  // Thus this the "if" statement in the following line.
  if (link_name.empty() || link_name == "world") {
    return plant.world_body();
  } else {
    const auto [parent_model_instance, local_name] =
        GetResolvedModelInstanceAndLocalName(link_name, model_instance, plant);

    return plant.GetBodyByName(local_name, parent_model_instance);
  }
}

// Extracts a Vector3d representation of the joint axis for joints with an axis.
Vector3d ExtractJointAxis(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint_spec) {
  DRAKE_DEMAND(joint_spec.Type() == sdf::JointType::REVOLUTE ||
               joint_spec.Type() == sdf::JointType::SCREW ||
               joint_spec.Type() == sdf::JointType::PRISMATIC ||
               joint_spec.Type() == sdf::JointType::CONTINUOUS);

  // Axis specification.
  const sdf::JointAxis* axis = joint_spec.Axis();
  if (axis == nullptr) {
    std::string message = fmt::format(
        "An axis must be specified for joint '{}'",
        joint_spec.Name());
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return Vector3d(0, 0, 1);
  }

  // Joint axis in the joint frame J.
  Vector3d axis_J = ResolveAxisXyz(diagnostic, *axis);
  return axis_J;
}

// Extracts a Vector3d representation of `axis` and `axis2` for joints with both
// attributes. Both axes are required. Otherwise, an error is triggered.
std::pair<Vector3d, Vector3d> ExtractJointAxisAndAxis2(
     const SDFormatDiagnostic& diagnostic, const sdf::Joint& joint_spec) {
  DRAKE_DEMAND(joint_spec.Type() == sdf::JointType::REVOLUTE2 ||
      joint_spec.Type() == sdf::JointType::UNIVERSAL);

  // Axis specification.
  const sdf::JointAxis* axis = joint_spec.Axis(0);
  const sdf::JointAxis* axis2 = joint_spec.Axis(1);
  if (axis == nullptr || axis2 == nullptr) {
    std::string message = fmt::format(
        "Both axis and axis2 must be specified for joint '{}'",
        joint_spec.Name());
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return std::make_pair(Vector3d(1, 0, 0), Vector3d(0, 1, 0));
  }

  // Joint axis and axis2 in the joint frame J.
  Vector3d axis_J = ResolveAxisXyz(diagnostic, *axis);
  Vector3d axis2_J = ResolveAxisXyz(diagnostic, *axis2);
  return std::make_pair(axis_J, axis2_J);
}

// Helper to parse the damping for a given joint specification.
// Right now we only parse the <damping> tag.
double ParseJointDamping(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint_spec) {
  DRAKE_DEMAND(joint_spec.Type() == sdf::JointType::REVOLUTE ||
      joint_spec.Type() == sdf::JointType::PRISMATIC ||
      joint_spec.Type() == sdf::JointType::SCREW ||
      joint_spec.Type() == sdf::JointType::UNIVERSAL ||
      joint_spec.Type() == sdf::JointType::BALL ||
      joint_spec.Type() == sdf::JointType::CONTINUOUS);

  // If the axis is missing, we'll rely on ExtractJointAxis to tell the user.
  // For our purposes in this function, it's OK to just bail and return zero.
  const sdf::JointAxis* axis = joint_spec.Axis();
  if (axis == nullptr) {
    return 0.0;
  }
  const double damping = axis->Damping();
  if (damping < 0) {
    std::string message = fmt::format(
        "Joint damping is negative for joint '{}'. "
        "Joint damping must be a non-negative number.",
        joint_spec.Name());
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return 0.0;
  }
  // If there are more than one axis (e.g. universal joint), we ignore the
  // damping specified for the second axis.
  const sdf::JointAxis* axis2 = joint_spec.Axis(1);
  if (axis2 != nullptr) {
    const double damping2 = axis2->Damping();
    if (damping2 != damping) {
      std::string message = fmt::format(
          "Joint damping must be equal for both axes for joint {}. "
          "The damping coefficient for 'axis' ({}) is used. The "
          "value for 'axis2' ({}) is ignored. The damping coefficient "
          "for 'axis2' should be explicitly defined as {} to match that for "
          "'axis'.",
          joint_spec.Name(), damping, damping2, damping);
      diagnostic.Warning(joint_spec.Element(), std::move(message));
    }
  }

  return damping;
}

// We interpret a value of exactly zero to specify un-actuated joints. Thus, the
// user would say <effort>0</effort>. The effort_limit should be non-negative.
// SDFormat internally interprets a negative effort limit as infinite and only
// returns non-negative values.
double GetEffortLimit(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint_spec, int axis_index) {
  DRAKE_DEMAND(axis_index == 0 || axis_index == 1);
  const sdf::JointAxis* axis = joint_spec.Axis(axis_index);
  if (axis == nullptr) {
    std::string message = fmt::format(
        "An axis{} must be specified for joint '{}'",
        axis_index > 0 ? "2" : "",
        joint_spec.Name());
    diagnostic.Warning(joint_spec.Element(), std::move(message));
    return 0.0;
  }
  return axis->Effort();
}

// Extracts the effort limit from a joint specification and adds an actuator if
// the value is non-zero. In SDFormat, effort limits are specified in
// <joint><axis><limit><effort>. In Drake, we understand that joints with an
// effort limit of zero are not actuated. For joint types that do not have an
// actuator implementation available in Drake, produces a diagnostic warning.
void AddJointActuatorFromSpecification(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint_spec, const Joint<double>& joint,
    MultibodyPlant<double>* plant) {
  DRAKE_THROW_UNLESS(plant != nullptr);
  DRAKE_DEMAND(joint_spec.Type() == sdf::JointType::BALL ||
               joint_spec.Type() == sdf::JointType::SCREW ||
               joint_spec.Type() == sdf::JointType::UNIVERSAL ||
               joint_spec.Type() == sdf::JointType::PRISMATIC ||
               joint_spec.Type() == sdf::JointType::REVOLUTE ||
               joint_spec.Type() == sdf::JointType::CONTINUOUS);

  // Ball joints do not have an axis (nor actuation). However, Drake still
  // permits the declaration of a first axis in order to specify damping, but
  // it should not have actuation, nor should there be a second axis.
  if (joint_spec.Type() == sdf::JointType::BALL) {
    if (joint_spec.Axis(0) != nullptr) {
      std::string message = fmt::format(
          "A ball joint axis will be ignored. Only the dynamic parameters"
          " and limits will be considered.", joint_spec.Name());
      diagnostic.Warning(joint_spec.Element(), std::move(message));
      if (GetEffortLimit(diagnostic, joint_spec, 0) != 0) {
        std::string effort_message = fmt::format(
            "Actuation (via non-zero effort limits) for ball joint '{}' is"
            " not implemented yet and will be ignored.",
            joint_spec.Name());
        diagnostic.Warning(joint_spec.Element(), std::move(effort_message));
      }
    }
    if (joint_spec.Axis(1) != nullptr) {
      std::string message = fmt::format(
          "An axis2 may not be specified for ball joint '{}' and will be "
          "ignored", joint_spec.Name());
      diagnostic.Warning(joint_spec.Element(), std::move(message));
    }
    return;
  }

  // Universal joints should have both axes defined per SDFormat, but Drake
  // does not yet implement a 2-dof actuator for this joint type.
  if (joint_spec.Type() == sdf::JointType::UNIVERSAL) {
    if (GetEffortLimit(diagnostic, joint_spec, 0) != 0 ||
        GetEffortLimit(diagnostic, joint_spec, 1) != 0) {
      std::string message = fmt::format(
          "Actuation (via non-zero effort limits) for universal joint '{}' is"
          " not implemented yet and will be ignored.",
          joint_spec.Name());
      diagnostic.Warning(joint_spec.Element(), std::move(message));
    }
    return;
  }

  // Prismatic, screw, revolute, and continuous joints have a single axis.
  const double effort_limit = GetEffortLimit(diagnostic, joint_spec, 0);
  if (effort_limit != 0) {
    const JointActuator<double>& actuator =
        plant->AddJointActuator(joint_spec.Name(), joint, effort_limit);

    // Parse and add the optional drake:rotor_inertia parameter.
    if (joint_spec.Element()->HasElement("drake:rotor_inertia")) {
      plant->get_mutable_joint_actuator(actuator.index())
          .set_default_rotor_inertia(
              joint_spec.Element()->Get<double>("drake:rotor_inertia"));
    }

    // Parse and add the optional drake:gear_ratio parameter.
    if (joint_spec.Element()->HasElement("drake:gear_ratio")) {
      plant->get_mutable_joint_actuator(actuator.index())
          .set_default_gear_ratio(
              joint_spec.Element()->Get<double>("drake:gear_ratio"));
    }

    // Parse and add the optional drake:controller_gains parameter.
    if (joint_spec.Element()->HasElement("drake:controller_gains")) {
      sdf::ElementPtr controller_gains =
          joint_spec.Element()->GetElement("drake:controller_gains");

      const bool has_p = controller_gains->HasAttribute("p");
      const bool has_d = controller_gains->HasAttribute("d");

      if (!has_p) {
        std::string message =
            "<drake:controller_gains>: Unable to find the 'p' attribute.";
        diagnostic.Error(controller_gains, std::move(message));
      }
      if (!has_d) {
        std::string message =
            "<drake:controller_gains>: Unable to find the 'd' attribute.";
        diagnostic.Error(controller_gains, std::move(message));
      }

      if (has_p && has_d) {
        const double p = controller_gains->Get<double>("p");
        const double d = controller_gains->Get<double>("d");

        plant->get_mutable_joint_actuator(actuator.index())
            .set_controller_gains({p, d});
      }
    }
  }
  if (joint_spec.Axis(1) != nullptr) {
    std::string message = fmt::format(
        "An axis2 may not be specified for 1-dof joint '{}' and will be "
        "ignored", joint_spec.Name());
    diagnostic.Warning(joint_spec.Element(), std::move(message));
  }
}

// Extracts the spring stiffness and the spring reference from a joint
// specification and adds a prismatic spring force element with the
// corresponding spring reference if the spring stiffness is nonzero.
// Only available for "prismatic" joints. The units for spring
// reference is meter and the units for spring stiffness is N/m.
void AddPrismaticSpringFromSpecification(const SDFormatDiagnostic& diagnostic,
                                         const sdf::Joint& joint_spec,
                                         const PrismaticJoint<double>& joint,
                                         MultibodyPlant<double>* plant) {
  DRAKE_THROW_UNLESS(plant != nullptr);
  DRAKE_THROW_UNLESS(joint_spec.Type() == sdf::JointType::PRISMATIC);

  // Axis specification.
  const sdf::JointAxis* axis = joint_spec.Axis();
  if (axis == nullptr) {
    std::string message = fmt::format(
        "An axis must be specified for joint '{}'.",
        joint_spec.Name());
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return;
  }

  const double spring_reference = axis->SpringReference();
  const double spring_stiffness = axis->SpringStiffness();

  // We add a force element if stiffness is positive, report error
  // if the stiffness is negative, and pass if the stiffness is zero
  if (spring_stiffness > 0) {
    plant->AddForceElement<PrismaticSpring>(
      joint, spring_reference, spring_stiffness);
  } else if (spring_stiffness < 0) {
    std::string message = fmt::format(
        "The stiffness specified for joint '{}' must be non-negative.",
        joint_spec.Name());
    diagnostic.Error(joint_spec.Element(), std::move(message));
  }
}

// Extracts the spring stiffness and the spring reference from a joint
// specification and adds a revolute spring force element with the
// corresponding spring reference if the spring stiffness is nonzero.
// Only available for "revolute" and "continuous" joints. The units for spring
// reference is radians and the units for spring stiffness is N⋅m/rad.
// When the error diagnostic policy is not set to throw this function will
// return false on errors.
bool AddRevoluteSpringFromSpecification(
    const SDFormatDiagnostic& diagnostic, const sdf::Joint &joint_spec,
    const RevoluteJoint<double>& joint, MultibodyPlant<double>* plant) {
  DRAKE_THROW_UNLESS(plant != nullptr);
  DRAKE_THROW_UNLESS(joint_spec.Type() == sdf::JointType::REVOLUTE ||
                     joint_spec.Type() == sdf::JointType::CONTINUOUS);

  // Axis specification.
  const sdf::JointAxis* axis = joint_spec.Axis();
  if (axis == nullptr) {
    std::string message = "An axis must be specified for joint '"
        + joint_spec.Name() + "'";
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return false;
  }

  const double spring_reference = axis->SpringReference();
  const double spring_stiffness = axis->SpringStiffness();

  // We don't add a force element if stiffness is zero.
  // If a negative value is passed in, RevoluteSpring will
  // throw an error.
  if (spring_stiffness != 0) {
    plant->AddForceElement<RevoluteSpring>(
      joint, spring_reference, spring_stiffness);
  }

  return true;
}

// Returns joint limits as the tuple (lower_limit, upper_limit,
// velocity_limit, acceleration_limit).  The units of the limits depend on the
// particular joint type.  For prismatic joints, units are meters for the
// position limits and m/s for the velocity limit.  For revolute, units
// are radians for the position limits and rad/s for the velocity limit. For
// continuous, positions limits are infinities and velocity limits have units
// rad/s. Velocity and acceleration limits are always >= 0.  This method throws
// an exception if the joint type is not one of revolute, prismatic, or
// continuous. When the diagnostic policy is not set to throw it will return
// std::nullopt on errors.
std::optional<std::tuple<double, double, double, double>> ParseJointLimits(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Joint& joint_spec) {
  DRAKE_THROW_UNLESS(joint_spec.Type() == sdf::JointType::REVOLUTE ||
                     joint_spec.Type() == sdf::JointType::PRISMATIC ||
                     joint_spec.Type() == sdf::JointType::CONTINUOUS);
  // Axis specification.
  const sdf::JointAxis* axis = joint_spec.Axis();
  if (axis == nullptr) {
    std::string message = "An axis must be specified for joint '"
        + joint_spec.Name() + "'";
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return std::nullopt;
  }

  // As of libsdformat13, ±∞ are used for axes with no position limits,
  // so no special handling is needed.
  const double lower_limit = axis->Lower();
  const double upper_limit = axis->Upper();
  if (lower_limit > upper_limit) {
    std::string message = "The lower limit must be lower (or equal) than "
        "the upper limit for joint '" + joint_spec.Name() + "'.";
    diagnostic.Error(joint_spec.Element(), std::move(message));
    return std::nullopt;
  }

  // SDFormat internally interprets a negative velocity limit as infinite and
  // only returns non-negative values.
  const double velocity_limit = axis->MaxVelocity();

  // Read Drake-namespaced acceleration limit if present. If not, default to
  // ±numeric_limits<double>::infinity().
  double acceleration_limit = std::numeric_limits<double>::infinity();
  if (axis->Element()->HasElement("limit")) {
    const auto limit_element = axis->Element()->GetElement("limit");
    const std::set<std::string> supported_limit_elements{
      "drake:acceleration",
      "effort",
      "lower",
      "stiffness",
      "upper",
      "velocity"};
    CheckSupportedElements(diagnostic, limit_element,
                           supported_limit_elements);

    if (limit_element->HasElement("drake:acceleration")) {
      acceleration_limit = limit_element->Get<double>("drake:acceleration");
      if (acceleration_limit < 0) {
        std::string message = "Acceleration limit is negative for joint '"
            + joint_spec.Name() + "'. Aceleration limit must be a non-negative"
            " number.";
        diagnostic.Error(limit_element, std::move(message));
        return std::tuple<double, double, double, double>();
      }
    }
  }

  return std::make_tuple(
      lower_limit, upper_limit, velocity_limit, acceleration_limit);
}

// Helper method to add joints to a MultibodyPlant given an sdf::Joint
// specification object. X_WM should be an identity when adding a world
// joint (is_model_joint = false) since a world joint doesn't have a
// containing model, hence M = W.
// If the diagnostic error policy is not set to throw it returns false
// when an error occurs.
bool AddJointFromSpecification(
    const SDFormatDiagnostic& diagnostic, const RigidTransformd& X_WM,
    const sdf::Joint& joint_spec, ModelInstanceIndex model_instance,
    MultibodyPlant<double>* plant, std::set<sdf::JointType>* joint_types,
    bool is_model_joint = true) {

  const std::set<std::string> supported_joint_elements{
    "axis",
    "axis2",
    "child",
    "drake:rotor_inertia",
    "drake:gear_ratio",
    "drake:controller_gains",
    "drake:mimic",
    "parent",
    "pose",
    "screw_thread_pitch"};
  CheckSupportedElements(diagnostic, joint_spec.Element(),
                         supported_joint_elements);

  // Axis elements should be fully supported, let sdformat validate those.

  const Body<double>& parent_body = GetBodyByLinkSpecificationName(
      ResolveJointParentLinkName(diagnostic, joint_spec), model_instance,
      *plant);
  const Body<double>& child_body = GetBodyByLinkSpecificationName(
      ResolveJointChildLinkName(diagnostic, joint_spec), model_instance,
      *plant);

  // Get the pose of frame J in the frame of the child link C, as specified in
  // <joint> <pose> ... </pose></joint>. The default `relative_to` pose of a
  // joint will be the child link.
  const RigidTransformd X_CJ = ResolveRigidTransform(
      diagnostic, joint_spec.SemanticPose(),
      GetRelativeBodyName(child_body, model_instance, *plant));

  // Pose of the frame J in the parent body frame P.
  std::optional<RigidTransformd> X_PJ;
  // We need to treat the world case separately since sdformat does not create
  // a "world" link from which we can request its pose (which in that case would
  // be the identity).
  const std::string relative_to = (is_model_joint) ? "__model__" : "world";
  if (parent_body.index() == world_index()) {
    const RigidTransformd X_MJ = ResolveRigidTransform(
        diagnostic, joint_spec.SemanticPose(), relative_to);
    X_PJ = X_WM * X_MJ;  // Since P == W.
  } else {
    X_PJ = ResolveRigidTransform(
        diagnostic, joint_spec.SemanticPose(),
        GetRelativeBodyName(parent_body, model_instance, *plant));
  }

  // If P and J are coincident, we won't create a new frame for J, but use frame
  // P directly. We indicate that by passing a nullopt.
  if (X_PJ.value().IsExactlyIdentity()) X_PJ = std::nullopt;

  // These will only be populated for prismatic and revolute joints.
  double lower_limit = 0;
  double upper_limit = 0;
  double velocity_limit = 0;
  double acceleration_limit = 0;

  switch (joint_spec.Type()) {
    case sdf::JointType::FIXED: {
      plant->AddJoint<WeldJoint>(
          joint_spec.Name(),
          parent_body, X_PJ,
          child_body, X_CJ,
          RigidTransformd::Identity() /* X_JpJc */);
      break;
    }
    case sdf::JointType::PRISMATIC: {
      const double damping = ParseJointDamping(diagnostic, joint_spec);
      Vector3d axis_J = ExtractJointAxis(diagnostic, joint_spec);
      std::optional<std::tuple<double, double, double, double>> joint_limits =
          ParseJointLimits(diagnostic, joint_spec);
      if (!joint_limits.has_value()) return false;
      std::tie(lower_limit, upper_limit, velocity_limit, acceleration_limit) =
          *joint_limits;
      const auto& joint = plant->AddJoint<PrismaticJoint>(
          joint_spec.Name(),
          parent_body, X_PJ,
          child_body, X_CJ, axis_J, lower_limit, upper_limit, damping);
      plant->get_mutable_joint(joint.index()).set_velocity_limits(
          Vector1d(-velocity_limit), Vector1d(velocity_limit));
      plant->get_mutable_joint(joint.index()).set_acceleration_limits(
          Vector1d(-acceleration_limit), Vector1d(acceleration_limit));
      AddJointActuatorFromSpecification(diagnostic, joint_spec, joint, plant);
      AddPrismaticSpringFromSpecification(diagnostic, joint_spec, joint, plant);
      break;
    }
    case sdf::JointType::REVOLUTE: {
      const double damping = ParseJointDamping(diagnostic, joint_spec);
      Vector3d axis_J = ExtractJointAxis(diagnostic, joint_spec);
      std::optional<std::tuple<double, double, double, double>> joint_limits =
          ParseJointLimits(diagnostic, joint_spec);
      if (!joint_limits.has_value()) return false;
      std::tie(lower_limit, upper_limit, velocity_limit, acceleration_limit) =
          *joint_limits;
      const auto& joint = plant->AddJoint<RevoluteJoint>(
          joint_spec.Name(),
          parent_body, X_PJ,
          child_body, X_CJ, axis_J, lower_limit, upper_limit, damping);
      plant->get_mutable_joint(joint.index()).set_velocity_limits(
          Vector1d(-velocity_limit), Vector1d(velocity_limit));
      plant->get_mutable_joint(joint.index()).set_acceleration_limits(
          Vector1d(-acceleration_limit), Vector1d(acceleration_limit));
      AddJointActuatorFromSpecification(diagnostic, joint_spec, joint, plant);
      if (!AddRevoluteSpringFromSpecification(
              diagnostic, joint_spec, joint, plant)) {
        return false;
      }
      break;
    }
    case sdf::JointType::UNIVERSAL: {
      const double damping = ParseJointDamping(diagnostic, joint_spec);
      // In Drake's implementation of universal joint, the rotation axes are
      // built into the frames M and F; the first rotation is about Fx and the
      // second is about My. Therefore, we can't arbitrarily set M and F to be
      // J. We have to be more careful on how we define F and M so that the
      // joint imposes the correct kinematics.

      // Construct frame I and find X_PI and X_CI. See definition of frame I in
      // the class doc of UniversalJoint.
      auto [Ix_J, Iy_J] = ExtractJointAxisAndAxis2(diagnostic, joint_spec);
      // Safe to normalize as libsdformat parser would have generated an error
      // if the axes are zero.
      Ix_J.normalize();
      Iy_J.normalize();
      const Vector3d Iz_J = Ix_J.cross(Iy_J);
      Matrix3d R_JI;
      R_JI.col(0) = Ix_J;
      R_JI.col(1) = Iy_J;
      R_JI.col(2) = Iz_J;
      // We require that axis and axis2 are orthogonal. As a result, R_JI should
      // be a valid rotation matrix.
      if (!math::RotationMatrixd::IsValid(R_JI)) {
        std::string message = fmt::format(
            "axis and axis2 must be orthogonal for joint '{}'",
            joint_spec.Name());
        diagnostic.Error(joint_spec.Element(), std::move(message));
      } else {
        const RigidTransformd X_JI(math::RotationMatrix<double>{R_JI});
        // The value of X_PJ is the identity if X_PJ == nullopt.
        const RigidTransformd X_PI = X_PJ.has_value() ? (*X_PJ) * X_JI : X_JI;
        const RigidTransformd X_CI = X_CJ * X_JI;
        // Frames M and F should both coincide with I when rotation angles are
        // zero.
        const RigidTransformd& X_PF = X_PI;
        const RigidTransformd& X_CM = X_CI;
        const auto& joint = plant->AddJoint<UniversalJoint>(
            joint_spec.Name(),
            parent_body, X_PF,
            child_body, X_CM, damping);
        // At most, this prints a warning (it does not add an actuator).
        AddJointActuatorFromSpecification(diagnostic, joint_spec, joint, plant);
      }
      break;
    }
    case sdf::JointType::BALL: {
      const double damping = ParseJointDamping(diagnostic, joint_spec);
      const auto& joint = plant->AddJoint<BallRpyJoint>(
        joint_spec.Name(),
        parent_body, X_PJ,
        child_body, X_CJ, damping);
      // At most, this prints a warning (it does not add an actuator).
      AddJointActuatorFromSpecification(diagnostic, joint_spec, joint, plant);
      break;
    }
    case sdf::JointType::CONTINUOUS: {
      const double damping = ParseJointDamping(diagnostic, joint_spec);
      Vector3d axis_J = ExtractJointAxis(diagnostic, joint_spec);
      std::optional<std::tuple<double, double, double, double>> joint_limits =
          ParseJointLimits(diagnostic, joint_spec);
      if (!joint_limits.has_value()) return false;
      std::tie(lower_limit, upper_limit, velocity_limit, acceleration_limit) =
          *joint_limits;
      const auto& joint =
          plant->AddJoint<RevoluteJoint>(joint_spec.Name(), parent_body, X_PJ,
                                         child_body, X_CJ, axis_J, damping);
      plant->get_mutable_joint(joint.index())
          .set_velocity_limits(Vector1d(-velocity_limit),
                               Vector1d(velocity_limit));
      plant->get_mutable_joint(joint.index())
          .set_acceleration_limits(Vector1d(-acceleration_limit),
                                   Vector1d(acceleration_limit));
      AddJointActuatorFromSpecification(diagnostic, joint_spec, joint, plant);
      if (!AddRevoluteSpringFromSpecification(
              diagnostic, joint_spec, joint, plant)) {
        return false;
      }
      break;
    }
    case sdf::JointType::SCREW: {
      const double damping = ParseJointDamping(diagnostic, joint_spec);
      // The ScrewThreadPitch() API uses the same representation as
      // Drake's ScrewJoint class (meters / revolution, right-handed).
      const double screw_thread_pitch = joint_spec.ScrewThreadPitch();
      Vector3d axis_J = ExtractJointAxis(diagnostic, joint_spec);
      const auto& joint = plant->AddJoint<ScrewJoint>(
          joint_spec.Name(),
          parent_body, X_PJ,
          child_body, X_CJ, axis_J, screw_thread_pitch, damping);
      AddJointActuatorFromSpecification(diagnostic, joint_spec, joint, plant);
      break;
    }
    case sdf::JointType::GEARBOX: {
      // TODO(jwnimmer-tri) Demote this to a warning, possibly adding a
      // RevoluteJoint as an approximation (stopgap) in that case.
        std::string message = fmt::format(
          "Joint type (gearbox) not supported for joint '{}'.",
          joint_spec.Name());
      diagnostic.Error(joint_spec.Element(), std::move(message));
      break;
    }
    case sdf::JointType::REVOLUTE2: {
      // TODO(jwnimmer-tri) Demote this to a warning, possibly adding a
      // UniversalJoint as an approximation (stopgap) in that case.
      std::string message = fmt::format(
          "Joint type (revolute2) not supported for joint '{}'.",
          joint_spec.Name());
      diagnostic.Error(joint_spec.Element(), std::move(message));
      break;
    }
    case sdf::JointType::INVALID: {
      // In probably all cases, libsdformat will have already detected
      // this error and so Drake won't get an INVALID joint.
      DRAKE_UNREACHABLE();
    }
  }
  joint_types->insert(joint_spec.Type());
  return true;
}

// Helper method to parse a custom drake:mimic tag.
bool ParseMimicTag(const SDFormatDiagnostic& diagnostic,
                   const sdf::Joint& joint_spec,
                   ModelInstanceIndex model_instance,
                   MultibodyPlant<double>* plant) {
  if (!joint_spec.Element()->HasElement("drake:mimic")) return true;

  if (!plant->is_discrete() ||
      plant->get_discrete_contact_solver() != DiscreteContactSolver::kSap) {
    diagnostic.Warning(
        joint_spec.Element(),
        fmt::format("Joint '{}' specifies a drake:mimic element that will be "
                    "ignored. Mimic elements are currently only supported by "
                    "MultibodyPlant with a discrete time step and using "
                    "DiscreteContactSolver::kSap.",
                    joint_spec.Name()));
    return true;
  }

  sdf::ElementPtr mimic_node = joint_spec.Element()->GetElement("drake:mimic");

  if (!mimic_node->HasAttribute("joint")) {
    diagnostic.Error(
        mimic_node, fmt::format("Joint '{}' drake:mimic element is missing the "
                                "required 'joint' attribute.",
                                joint_spec.Name()));
    return false;
  }

  const std::string joint_to_mimic = mimic_node->Get<std::string>("joint");

  if (!plant->HasJointNamed(joint_to_mimic, model_instance)) {
    diagnostic.Error(
        mimic_node,
        fmt::format("Joint '{}' drake:mimic element specifies joint '{}' which"
                    " does not exist.",
                    joint_spec.Name(), joint_to_mimic));
    return false;
  }

  if (joint_to_mimic == joint_spec.Name()) {
    diagnostic.Error(mimic_node,
                     fmt::format("Joint '{}' drake:mimic element specifies "
                                 "joint '{}'. Joints cannot mimic themselves.",
                                 joint_spec.Name(), joint_to_mimic));
    return false;
  }

  if (!mimic_node->HasAttribute("multiplier")) {
    diagnostic.Error(
        mimic_node, fmt::format("Joint '{}' drake:mimic element is missing the "
                                "required 'multiplier' attribute.",
                                joint_spec.Name()));
    return false;
  }

  if (!mimic_node->HasAttribute("offset")) {
    diagnostic.Error(
        mimic_node, fmt::format("Joint '{}' drake:mimic element is missing the "
                                "required 'offset' attribute.",
                                joint_spec.Name()));
    return false;
  }

  const double gear_ratio = mimic_node->Get<double>("multiplier");
  const double offset = mimic_node->Get<double>("offset");

  const Joint<double>& joint0 =
      plant->GetJointByName(joint_spec.Name(), model_instance);
  const Joint<double>& joint1 =
      plant->GetJointByName(joint_to_mimic, model_instance);

  if (joint0.num_velocities() != 1 || joint1.num_velocities() != 1) {
    // The URDF documentation is ambiguous as to whether multi-dof joints
    // are supported by the mimic tag (which the drake:mimic tag is analogous
    // to). So we only raise a warning, not an error.
    diagnostic.Warning(
        mimic_node,
        fmt::format("Drake only supports the drake:mimic element for "
                    "single-dof joints. The joint '{}' (with {} "
                    "dofs) is attempting to mimic joint '{}' (with "
                    "{} dofs). The drake:mimic element will be ignored.",
                    joint0.name(), joint0.num_velocities(), joint_to_mimic,
                    joint1.num_velocities()));
  } else {
    plant->AddCouplerConstraint(joint0, joint1, gear_ratio, offset);
  }

  return true;
}

// Helper method to load an SDF file and read the contents into an sdf::Root
// object.
[[nodiscard]] sdf::Errors LoadSdf(
    const SDFormatDiagnostic& diagnostic,
    sdf::Root* root,
    const DataSource& data_source,
    const sdf::ParserConfig& parser_config) {
  sdf::Errors errors;
  if (data_source.IsFilename()) {
    const std::string full_path = data_source.GetAbsolutePath();
    errors = root->Load(full_path, parser_config);
  } else {
    errors = root->LoadSdfString(data_source.contents(), parser_config);
  }

  if (errors.empty()) {
    const std::set<std::string> supported_root_elements{
      "model",
      "world"};

    CheckSupportedElements(
        diagnostic, root->Element(), supported_root_elements);
  }
  return errors;
}

struct LinkInfo {
  const RigidBody<double>* body{};
  RigidTransformd X_WL;
};

// Helper method to add a model to a MultibodyPlant given an sdf::Model
// specification object.
std::optional<std::vector<LinkInfo>> AddLinksFromSpecification(
    const SDFormatDiagnostic& diagnostic,
    const ModelInstanceIndex model_instance,
    const sdf::Model& model,
    const RigidTransformd& X_WM,
    MultibodyPlant<double>* plant,
    const PackageMap& package_map,
    const std::string& root_dir) {
  std::vector<LinkInfo> link_infos;

  const std::set<std::string> supported_link_elements{
    "collision",
    "gravity",
    "inertial",
    "kinematic",
    "pose",
    "visual"};

  // Add all the links
  for (uint64_t link_index = 0; link_index < model.LinkCount(); ++link_index) {
    const sdf::Link& link = *model.LinkByIndex(link_index);
    sdf::ElementPtr link_element = link.Element();

    CheckSupportedElements(
        diagnostic, link_element, supported_link_elements);
    CheckSupportedElementValue(diagnostic, link_element, "kinematic", "false");
    CheckSupportedElementValue(diagnostic, link_element, "gravity", "true");

    // Get the link's inertia relative to the Bcm frame.
    // sdf::Link::Inertial() provides a representation for the SpatialInertia
    // M_Bcm_Bi of body B, about its center of mass Bcm, and expressed in an
    // inertial frame Bi as defined in <inertial> <pose></pose> </inertial>.
    // Per SDF specification, Bi's origin is at the COM Bcm, but Bi is not
    // necessarily aligned with B.
    const gz::math::Inertiald& Inertial_Bcm_Bi = link.Inertial();

    const SpatialInertia<double> M_BBo_B =
        ExtractSpatialInertiaAboutBoExpressedInB(
            diagnostic, link_element, Inertial_Bcm_Bi);

    // Add a rigid body to model each link.
    const RigidBody<double>& body =
        plant->AddRigidBody(link.Name(), model_instance, M_BBo_B);

    // Register information.
    const RigidTransformd X_ML = ResolveRigidTransform(
        diagnostic, link.SemanticPose());
    const RigidTransformd X_WL = X_WM * X_ML;
    link_infos.push_back(LinkInfo{&body, X_WL});

    // Set the initial pose of the free body (only use if the body is indeed
    // floating).
    plant->SetDefaultFreeBodyPose(body, X_WL);

    const std::set<std::string> supported_geometry_elements{
      "box",
      "capsule",
      "cylinder",
      "drake:capsule",
      "drake:ellipsoid",
      "ellipsoid",
      "empty",
      "mesh",
      "plane",
      "sphere"};

    if (plant->geometry_source_is_registered()) {
      ResolveFilename resolve_filename =
        [&package_map, &root_dir, &link_element](
          const SDFormatDiagnostic& inner_diagnostic, std::string uri) {
        return ResolveUri(inner_diagnostic.MakePolicyForNode(*link_element),
            uri, package_map, root_dir);
      };

      for (uint64_t visual_index = 0; visual_index < link.VisualCount();
           ++visual_index) {
        const sdf::Visual& sdf_visual = *link.VisualByIndex(visual_index);
        const sdf::Geometry& sdf_geometry = *sdf_visual.Geom();

        sdf::ElementPtr geometry_element = sdf_geometry.Element();
        CheckSupportedElements(
            diagnostic, geometry_element, supported_geometry_elements);

        const RigidTransformd X_LG = ResolveRigidTransform(
            diagnostic, sdf_visual.SemanticPose());
        std::optional<unique_ptr<GeometryInstance>> geometry_instance =
            MakeGeometryInstanceFromSdfVisual(
                diagnostic, sdf_visual, resolve_filename, X_LG);
        if (!geometry_instance.has_value()) return std::nullopt;
        // We check for nullptr in case someone decided to specify an SDF
        // <empty/> geometry.
        if (*geometry_instance) {
          // The parsing should *always* produce an IllustrationProperties
          // instance, even if it is empty.
          DRAKE_DEMAND(
              (*geometry_instance)->illustration_properties() != nullptr);

          plant->RegisterVisualGeometry(
              body, (*geometry_instance)->pose(),
              (*geometry_instance)->shape(),
              (*geometry_instance)->name(),
              *(*geometry_instance)->illustration_properties());
        }
      }

      for (uint64_t collision_index = 0;
           collision_index < link.CollisionCount(); ++collision_index) {
        const sdf::Collision& sdf_collision =
            *link.CollisionByIndex(collision_index);
        const sdf::Geometry& sdf_geometry = *sdf_collision.Geom();

        sdf::ElementPtr geometry_element = sdf_geometry.Element();
        CheckSupportedElements(
            diagnostic, geometry_element, supported_geometry_elements);

        std::optional<std::unique_ptr<geometry::Shape>> shape =
            MakeShapeFromSdfGeometry(
                diagnostic, sdf_geometry, resolve_filename);
        if (!shape.has_value()) return std::nullopt;
        if (*shape != nullptr) {
          const RigidTransformd X_LG = ResolveRigidTransform(
              diagnostic, sdf_collision.SemanticPose());
          const RigidTransformd X_LC =
              MakeGeometryPoseFromSdfCollision(sdf_collision, X_LG);
          std::optional<geometry::ProximityProperties> props =
              MakeProximityPropertiesForCollision(diagnostic, sdf_collision);
          if (!props.has_value()) return std::nullopt;
          plant->RegisterCollisionGeometry(body, X_LC, **shape,
                                           sdf_collision.Name(),
                                           std::move(*props));
        }
      }
    }
  }
  return link_infos;
}

const Frame<double>& AddFrameFromSpecification(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Frame& frame_spec, ModelInstanceIndex model_instance,
    const Frame<double>& default_frame, MultibodyPlant<double>* plant) {
  const Frame<double>* parent_frame{};
  const RigidTransformd X_PF = ResolveRigidTransform(
      diagnostic, frame_spec.SemanticPose(), frame_spec.AttachedTo());
  if (frame_spec.AttachedTo().empty()) {
    parent_frame = &default_frame;
  } else {
    const std::string attached_to_absolute_name = ScopedName::Join(
        GetInstanceScopeNameIgnoringWorld(*plant, model_instance),
        frame_spec.AttachedTo()).to_string();

    // If the attached_to refers to a model, we use the `__model__` frame
    // associated with the model.
    if (plant->HasModelInstanceNamed(attached_to_absolute_name)) {
      const auto attached_to_model_instance =
          plant->GetModelInstanceByName(attached_to_absolute_name);
      parent_frame =
          &plant->GetFrameByName("__model__", attached_to_model_instance);
    } else {
      const auto [parent_model_instance, local_name] =
          GetResolvedModelInstanceAndLocalName(frame_spec.AttachedTo(),
                                               model_instance, *plant);
      if (plant->HasFrameNamed(local_name, parent_model_instance)) {
        parent_frame =
            &plant->GetFrameByName(local_name, parent_model_instance);
      } else {
        // If there is no frame named `local_name`, the `attached_to` attribute
        // must be pointing to something we don't create implicit frames for in
        // Drake. Currently these are models and joints. Models are handled in
        // the first `if` block, so we're dealing with joints here. Since joints
        // may end up in a model instance different from the model in which they
        // were defined, we don't bother to find the joint and use its child
        // frame. Instead we ask libsdformat to resolve the body associated with
        // whatever is referenced by the `attached_to` attribute. Since this is
        // a body, we're assured that its implicit frame exists in the plant.
        std::string resolved_attached_to_body_name;
        sdf::Errors errors = frame_spec.ResolveAttachedToBody(
            resolved_attached_to_body_name);
        diagnostic.PropagateErrors(errors);
        const std::string resolved_attached_to_body_absolute_name =
            ScopedName::Join(
                GetInstanceScopeNameIgnoringWorld(*plant, model_instance),
                resolved_attached_to_body_name).to_string();
        parent_frame = parsing::GetScopedFrameByNameMaybe(
            *plant, resolved_attached_to_body_absolute_name);
      }
    }
  }
  // Libsdformat will have already detected this error and
  // at this point the parent_frame wouldn't be a nullptr
  DRAKE_DEMAND(parent_frame != nullptr);

  const Frame<double>& frame =
      plant->AddFrame(std::make_unique<FixedOffsetFrame<double>>(
          frame_spec.Name(), *parent_frame, X_PF));
  return frame;
}

Eigen::Vector3d ParseVector3(const SDFormatDiagnostic& diagnostic,
                             const sdf::ElementPtr node,
                             const char* element_name) {
  if (!node->HasElement(element_name)) {
    std::string message = fmt::format(
        "<{}>: Unable to find the <{}> child tag.", node->GetName(),
        element_name);
    diagnostic.Error(node, message);
    return {};
  }

  auto value = node->Get<gz::math::Vector3d>(element_name);

  return ToVector3(value);
}

const Frame<double>* ParseFrame(const SDFormatDiagnostic& diagnostic,
                                const sdf::ElementPtr node,
                                ModelInstanceIndex model_instance,
                                MultibodyPlant<double>* plant,
                                const char* element_name) {
  if (!node->HasElement(element_name)) {
    std::string message = fmt::format(
        "<{}>: Unable to find the <{}> child tag.",
        node->GetName(), element_name);
    diagnostic.Error(node, std::move(message));
    return nullptr;
  }

  // TODO(rpoyner-tri): In theory, we could use
  // GetResolvedModelInstanceAndLocalName here. In practice, it doesn't turn
  // out well, because that function just allows MultibodyPlant code to throw
  // exceptions, instead of checking for bad inputs and issuing model source
  // code locatable diagnostics. Consider harmonizing the various
  // implementations with better checking and diagnostic messages.

  // Support references to frames
  // in nested models. Note: only downward nested references are permitted.
  // See http://sdformat.org/tutorials?tut=composition#composition
  const std::string current_model_name =
      plant->GetModelInstanceName(model_instance);
  const std::string frame_name = node->Get<std::string>(element_name);
  auto absolute_scoped_name = ScopedName::Join(current_model_name, frame_name);

  const std::string search_model_name(absolute_scoped_name.get_namespace());
  if (!plant->HasModelInstanceNamed(search_model_name)) {
    std::string message = fmt::format(
          "<{}>: Model instance name '{}' (implied by frame name '{}' in <{}>"
          " within model instance '{}') does not exist in the model.",
          node->GetName(), search_model_name, frame_name, element_name,
          current_model_name);
    diagnostic.Error(node, std::move(message));
    return nullptr;
  }

  ModelInstanceIndex search_model_instance =
      plant->GetModelInstanceByName(search_model_name);
  const std::string search_frame_name(absolute_scoped_name.get_element());

  if (!plant->HasFrameNamed(search_frame_name, search_model_instance)) {
    std::string message = fmt::format(
          "<{}>: Frame '{}' specified for <{}> does not exist in the model.",
          node->GetName(), frame_name, element_name);
    diagnostic.Error(node, std::move(message));
    return nullptr;
  }

  return &plant->GetFrameByName(search_frame_name, search_model_instance);
}

const Body<double>* ParseBody(const SDFormatDiagnostic& diagnostic,
                              const sdf::ElementPtr node,
                              ModelInstanceIndex model_instance,
                              MultibodyPlant<double>* plant,
                              const char* element_name) {
  if (!node->HasElement(element_name)) {
    std::string message =
        fmt::format("<{}>: Unable to find the <{}> child tag.", node->GetName(),
                    element_name);
    diagnostic.Error(node, std::move(message));
    return nullptr;
  }

  const std::string body_name = node->Get<std::string>(element_name);

  if (!plant->HasBodyNamed(body_name, model_instance)) {
    std::string message = fmt::format(
        "<{}>: Body '{}' specified for <{}> does not exist in the model.",
        node->GetName(), body_name, element_name);
    diagnostic.Error(node, std::move(message));
    return nullptr;
  }

  return &plant->GetBodyByName(body_name, model_instance);
}

// TODO(eric.cousineau): Update parsing pending resolution of
// https://github.com/osrf/sdformat/issues/288
// When diagnostic policy is not set to throw it returns false on errors.
bool AddDrakeJointFromSpecification(const SDFormatDiagnostic& diagnostic,
                                    const sdf::ElementPtr node,
                                    ModelInstanceIndex model_instance,
                                    MultibodyPlant<double>* plant) {
  const std::set<std::string> supported_joint_elements{
    "drake:parent",
    "drake:child",
    "drake:damping",
    "pose"};
  CheckSupportedElements(diagnostic, node, supported_joint_elements);

  if (!node->HasAttribute("type")) {
    std::string message =
        "<drake:joint>: Unable to find the 'type' attribute.";
    diagnostic.Error(node, std::move(message));
    return false;
  }
  const std::string joint_type = node->Get<std::string>("type");
  if (!node->HasAttribute("name")) {
    std::string message =
        "<drake:joint>: Unable to find the 'name' attribute.";
    diagnostic.Error(node, std::move(message));
    return false;
  }
  const std::string joint_name = node->Get<std::string>("name");

  // TODO(eric.cousineau): Add support for parsing joint pose.
  if (node->HasElement("pose")) {
    std::string message =
        "<drake:joint> does not yet support the <pose> child tag.";
    diagnostic.Error(node, std::move(message));
    return false;
  }

  const Frame<double>* parent_frame =
      ParseFrame(diagnostic, node, model_instance, plant, "drake:parent");
  if (parent_frame == nullptr) { return false; }
  const Frame<double>* child_frame =
      ParseFrame(diagnostic, node, model_instance, plant, "drake:child");
  if (child_frame == nullptr) { return false; }

  if (joint_type == "planar") {
    // TODO(eric.cousineau): Error out when there are unused tags.
    Vector3d damping = ParseVector3(diagnostic, node, "drake:damping");
    plant->AddJoint(std::make_unique<PlanarJoint<double>>(
        joint_name, *parent_frame, *child_frame, damping));
  } else {
    std::string message = "ERROR: <drake:joint> '" + joint_name +
        "' has unrecognized value for 'type' attribute: " + joint_type;
    diagnostic.Error(node, std::move(message));
    return false;
  }
  return true;
}

const LinearBushingRollPitchYaw<double>* AddBushingFromSpecification(
    const SDFormatDiagnostic& diagnostic,
    const sdf::ElementPtr node,
    ModelInstanceIndex model_instance,
    MultibodyPlant<double>* plant) {
  const std::set<std::string> supported_bushing_elements{
    "drake:bushing_frameA",
    "drake:bushing_frameC",
    "drake:bushing_force_damping",
    "drake:bushing_force_stiffness",
    "drake:bushing_torque_damping",
    "drake:bushing_torque_stiffness"};
  CheckSupportedElements(diagnostic, node, supported_bushing_elements);

  // Functor to read a vector valued child tag with tag name: `element_name`
  // e.g. <element_name>0 0 0</element_name>
  // Throws an error if the tag does not exist.
  auto read_vector = [&diagnostic, node](
      const char* element_name) -> Eigen::Vector3d {
    return ParseVector3(diagnostic, node, element_name);
  };

  // Functor to read a child tag with tag name: `element_name` that specifies a
  // frame name, e.g. <element_name>frame_name</element_name>
  // Throws an error if the tag does not exist or if the frame does not exist in
  // the plant.
  auto read_frame = [&diagnostic, node, model_instance, plant](
      const char* element_name) -> const Frame<double>* {
    return ParseFrame(diagnostic, node, model_instance, plant, element_name);
  };

  return ParseLinearBushingRollPitchYaw(read_vector, read_frame, plant);
}

std::optional<MultibodyConstraintId> AddBallConstraintFromSpecification(
    const SDFormatDiagnostic& diagnostic, const sdf::ElementPtr node,
    ModelInstanceIndex model_instance, MultibodyPlant<double>* plant) {
  const std::set<std::string> supported_ball_constraint_elements{
      "drake:ball_constraint_body_A",
      "drake:ball_constraint_p_AP",
      "drake:ball_constraint_body_B",
      "drake:ball_constraint_p_BQ",
  };
  CheckSupportedElements(diagnostic, node, supported_ball_constraint_elements);

  // Functor to read a vector valued child tag with tag name: `element_name`
  // e.g. <element_name>0 0 0</element_name>
  // Throws an error if the tag does not exist.
  auto read_vector = [&diagnostic,
                      node](const char* element_name) -> Eigen::Vector3d {
    return ParseVector3(diagnostic, node, element_name);
  };

  // Functor to read a child tag with tag name: `element_name` that specifies a
  // body name, e.g. <element_name>body_name</element_name>
  // Throws an error if the tag does not exist or if the body does not exist in
  // the plant.
  auto read_body = [&diagnostic, node, model_instance, plant](
                       const char* element_name) -> const Body<double>* {
    return ParseBody(diagnostic, node, model_instance, plant, element_name);
  };

  return ParseBallConstraint(read_vector, read_body, plant);
}

// Helper to determine if two links are welded together.
bool AreWelded(
    const MultibodyPlant<double>& plant, const Body<double>& a,
    const Body<double>& b) {
  for (auto* body : plant.GetBodiesWeldedTo(a)) {
    if (body == &b) {
      return true;
    }
  }
  return false;
}

void ParseCollisionFilterGroup(const SDFormatDiagnostic& diagnostic,
                               ModelInstanceIndex model_instance,
                               const sdf::Model& model,
                               MultibodyPlant<double>* plant,
                               CollisionFilterGroupResolver* resolver) {
  auto next_child_element = [](const ElementNode& data_element,
                               const char* element_name) {
    return std::get<sdf::ElementPtr>(data_element)
        ->GetElementImpl(std::string(element_name));
  };
  auto next_sibling_element = [](const ElementNode& data_element,
                                 const char* element_name) {
    return std::get<sdf::ElementPtr>(data_element)
        ->GetNextElement(std::string(element_name));
  };
  auto has_attribute = [](const ElementNode& data_element,
                          const char* attribute_name) {
    return std::get<sdf::ElementPtr>(data_element)
        ->HasAttribute(std::string(attribute_name));
  };
  auto get_string_attribute =
      [&diagnostic](const ElementNode& data_element,
                    const char* attribute_name) -> std::string {
        auto element = std::get<sdf::ElementPtr>(data_element);
        if (!element->HasAttribute(attribute_name)) {
          std::string message = fmt::format(
              "The tag <{}> is missing the required attribute \"{}\"",
              element->GetName(), attribute_name);
          diagnostic.Error(element, std::move(message));
          return {};
        }
        return std::get<sdf::ElementPtr>(data_element)
            ->Get<std::string>(attribute_name);
      };
  auto get_bool_attribute = [](const ElementNode& data_element,
                               const char* attribute_name) {
    return std::get<sdf::ElementPtr>(data_element)->Get<bool>(attribute_name);
  };
  auto read_tag_string =
      [&diagnostic](const ElementNode& data_element, const char*)
      -> std::string {
        auto element = std::get<sdf::ElementPtr>(data_element);
        sdf::ParamPtr param = element->GetValue();
        if (param == nullptr) {
          std::string message = fmt::format(
              "The tag <{}> is missing a required string value.",
              element->GetName());
          diagnostic.Error(element, std::move(message));
          return {};
        }
        return param->GetAsString();
      };
  ParseCollisionFilterGroupCommon(
      diagnostic.MakePolicyForNode(*(model.Element())),
      model_instance, model.Element(), plant, resolver,
      next_child_element, next_sibling_element, has_attribute,
      get_string_attribute, get_bool_attribute, read_tag_string);
}

bool CanReuseModelInstance(
    const ModelInstanceIndex& model_instance,
    const ModelInstanceIndexRange reusable_model_instance_range) {
  return model_instance >= reusable_model_instance_range.first &&
         model_instance < reusable_model_instance_range.second;
}

ModelInstanceIndex AddModelInstanceIfReusable(
    MultibodyPlant<double>* plant, const std::string& model_name,
    const ModelInstanceIndexRange& reusable_model_instance_range) {
  if (plant->HasModelInstanceNamed(model_name)) {
    auto model_instance = plant->GetModelInstanceByName(model_name);
    if (!CanReuseModelInstance(model_instance, reusable_model_instance_range)) {
      throw std::logic_error(
          "This model already contains a model instance named '" + model_name +
          "'. Model instance names must be unique within a given model.");
    }
    return model_instance;
  } else {
    return plant->AddModelInstance(model_name);
  }
}

// Helper class that does bookkeeping on the frame bearing elements of a given
// model instance in a MultibodyPlant. This is used to keep track of newly
// created elements after a custom parser is called so that we can create
// Interface API data structures from just the newly created elements.
// The Interface API step is necessary so that we can construct the appropriate
// pose graph at higher levels of the hierarchy using libsdformat. Note: We
// assume that Drake custom parsers do not support nested models.
// TODO(azeey) If any of Drakes custom parsers start supporting nested models,
// this needs to be updated to keep track of them.
class InterfaceModelHelper {
 public:
  explicit InterfaceModelHelper(const MultibodyPlant<double>& pt)
      : plant_(pt) {}

  std::vector<ModelInstanceIndex> GetChildModelInstanceIndices() const {
    std::vector<ModelInstanceIndex> output;

    for (ModelInstanceIndex mi(model_instance_ + 1);
         mi < plant_.num_model_instances(); ++mi) {
      if (multibody::ScopedName::Parse(plant_.GetModelInstanceName(mi))
              .get_element() == name_) {
        output.push_back(mi);
      }
    }
    return output;
  }

  // Take a snapshot of the current contents of a model instance so that we
  // later on do a diff
  void TakeSnapShot(ModelInstanceIndex model_instance_index) {
    model_instance_ = model_instance_index;
    name_ = plant_.GetModelInstanceName(model_instance_);
    body_indices_ = plant_.GetBodyIndices(model_instance_);
    frame_indices_ = plant_.GetFrameIndices(model_instance_);
    joint_indices_ = plant_.GetJointIndices(model_instance_);
    model_instance_indices_ = GetChildModelInstanceIndices();
    have_snapshot = true;
  }

  template <typename T>
  static std::vector<T> GetVectorDiff(const std::vector<T>& current,
                                      const std::vector<T>& previous) {
    std::vector<T> output;
    std::set_difference(current.begin(), current.end(), previous.begin(),
                        previous.end(), std::back_inserter(output));
    return output;
  }

  // Compute the diff to determine the elements that were created after the
  // snapshot.
  void ComputeDiffFromSnapshot() {
    DRAKE_DEMAND(have_snapshot);
    body_indices_ =
      GetVectorDiff(plant_.GetBodyIndices(model_instance_), body_indices_);
    frame_indices_ =
      GetVectorDiff(plant_.GetFrameIndices(model_instance_), frame_indices_);
    joint_indices_ =
      GetVectorDiff(plant_.GetJointIndices(model_instance_), joint_indices_);
    model_instance_indices_ =
      GetVectorDiff(GetChildModelInstanceIndices(), model_instance_indices_);
  }

  ModelInstanceIndex model_instance() const { return model_instance_; }

  const std::vector<JointIndex>& joint_indices() const {
    return joint_indices_;
  }
  const std::vector<FrameIndex>& frame_indices() const {
    return frame_indices_;
  }
  const std::vector<BodyIndex>& body_indices() const { return body_indices_; }
  const std::string& model_frame_name() const { return model_frame_name_; }
  void set_model_frame_name(const std::string& model_frame_name) {
    model_frame_name_ = model_frame_name;
  }

 private:
  const MultibodyPlant<double>& plant_;
  ModelInstanceIndex model_instance_;
  std::string name_;
  std::vector<BodyIndex> body_indices_;
  std::vector<FrameIndex> frame_indices_;
  std::vector<JointIndex> joint_indices_;
  std::vector<ModelInstanceIndex> model_instance_indices_;
  std::string model_frame_name_ = "__model__";

  bool have_snapshot{false};
};

// Helper method to add a model to a MultibodyPlant given an sdf::Model
// specification object.
std::vector<ModelInstanceIndex> AddModelsFromSpecification(
    const SDFormatDiagnostic& diagnostic,
    const sdf::Model& model,
    const std::string& model_name,
    const RigidTransformd& X_WP,
    MultibodyPlant<double>* plant,
    CollisionFilterGroupResolver* resolver,
    const PackageMap& package_map,
    const std::string& root_dir,
    const ModelInstanceIndexRange &reusable_model_instance_range) {
  const ModelInstanceIndex model_instance = AddModelInstanceIfReusable(
      plant, model_name, reusable_model_instance_range);

  const std::set<std::string> supported_model_elements{
    "drake:joint",
    "drake:linear_bushing_rpy",
    "drake:ball_constraint",
    "drake:collision_filter_group",
    "frame",
    "include",
    "joint",
    "link",
    "model",
    "pose",
    "static"};
  CheckSupportedElements(
      diagnostic, model.Element(), supported_model_elements);

  std::vector <ModelInstanceIndex> added_model_instances{model_instance};

  // "P" is the parent frame. If the model is in a child of //world or //sdf,
  // this will be the world frame. Otherwise, this will be the parent model
  // frame.
  const RigidTransformd X_PM = ResolveRigidTransform(
      diagnostic, model.SemanticPose());
  const RigidTransformd X_WM = X_WP * X_PM;

  // Add nested models at root-level of <model>.
  // Do this before the resolving canonical link because the link might be in a
  // nested model.
  drake::log()->trace("sdf_parser: Add nested models");
  for (uint64_t model_index = 0; model_index < model.ModelCount();
       ++model_index) {
    const sdf::Model& nested_model = *model.ModelByIndex(model_index);
    std::vector<ModelInstanceIndex> nested_model_instances =
        AddModelsFromSpecification(
            diagnostic, nested_model,
            sdf::JoinName(model_name, nested_model.Name()), X_WM, plant,
            resolver, package_map, root_dir, reusable_model_instance_range);

    added_model_instances.insert(added_model_instances.end(),
                                 nested_model_instances.begin(),
                                 nested_model_instances.end());
  }
  drake::log()->trace("sdf_parser: Add links");
  std::optional<std::vector<LinkInfo>> added_link_infos =
      AddLinksFromSpecification(diagnostic, model_instance, model,
                                X_WM, plant, package_map, root_dir);
  if (!added_link_infos.has_value()) return {};

  // Add the SDF "model frame" given the model name so that way any frames added
  // to the plant are associated with this current model instance.
  // N.B. This follows SDFormat's convention.
  const std::string sdf_model_frame_name = "__model__";

  drake::log()->trace("sdf_parser: Resolve canonical link");
  const Frame<double>& model_frame = [&]() -> const Frame<double>& {
    const auto [canonical_link, canonical_link_name] =
        model.CanonicalLinkAndRelativeName();

    if (canonical_link != nullptr) {
      const auto [parent_model_instance, local_name] =
          GetResolvedModelInstanceAndLocalName(canonical_link_name,
                                             model_instance, *plant);
      const Frame<double>& canonical_link_frame =
          plant->GetFrameByName(local_name, parent_model_instance);
      const RigidTransformd X_LcM = ResolveRigidTransform(
          diagnostic, model.SemanticPose(),
          sdf::JoinName(model.Name(), canonical_link_name));

      return plant->AddFrame(std::make_unique<FixedOffsetFrame<double>>(
          sdf_model_frame_name, canonical_link_frame, X_LcM, model_instance));
    } else {
      return plant->AddFrame(std::make_unique<FixedOffsetFrame<double>>(
          sdf_model_frame_name, plant->world_frame(), X_WM, model_instance));
    }
  }();

  drake::log()->trace("sdf_parser: Add joints");
  // Add all the joints
  std::set<sdf::JointType> joint_types;
  // TODO(eric.cousineau): Register frames from SDF once we have a pose graph.
  for (uint64_t joint_index = 0; joint_index < model.JointCount();
       ++joint_index) {
    // Get a pointer to the SDF joint, and the joint axis information.
    const sdf::Joint& joint = *model.JointByIndex(joint_index);
    if (!AddJointFromSpecification(
        diagnostic, X_WM, joint, model_instance, plant, &joint_types)) {
        return {};
      }
  }

  // Parse drake:mimic elements only after all joints have been added.
  for (uint64_t joint_index = 0; joint_index < model.JointCount();
       ++joint_index) {
    // Get a pointer to the SDF joint, and the joint axis information.
    const sdf::Joint& joint = *model.JointByIndex(joint_index);
    if (!ParseMimicTag(diagnostic, joint, model_instance, plant)) {
      return {};
    }
  }

  drake::log()->trace("sdf_parser: Add explicit frames");
  // Add frames at root-level of <model>.
  for (uint64_t frame_index = 0; frame_index < model.FrameCount();
       ++frame_index) {
    const sdf::Frame& frame = *model.FrameByIndex(frame_index);
    AddFrameFromSpecification(
        diagnostic, frame, model_instance, model_frame, plant);
  }

  drake::log()->trace("sdf_parser: Add drake custom joints");
  if (model.Element()->HasElement("drake:joint")) {
    for (sdf::ElementPtr joint_node =
             model.Element()->GetElement("drake:joint");
         joint_node; joint_node = joint_node->GetNextElement("drake:joint")) {
      if (!AddDrakeJointFromSpecification(
          diagnostic, joint_node, model_instance, plant)) {
          return {};
        }
    }
  }

  drake::log()->trace("sdf_parser: Add linear_bushing_rpy");
  if (model.Element()->HasElement("drake:linear_bushing_rpy")) {
    for (sdf::ElementPtr bushing_node =
             model.Element()->GetElement("drake:linear_bushing_rpy");
         bushing_node; bushing_node = bushing_node->GetNextElement(
                           "drake:linear_bushing_rpy")) {
      if (AddBushingFromSpecification(
          diagnostic, bushing_node, model_instance, plant) == nullptr) {
        return {};
      }
    }
  }

  drake::log()->trace("sdf_parser: Add BallConstraint");
  if (model.Element()->HasElement("drake:ball_constraint")) {
    for (sdf::ElementPtr constraint_node =
             model.Element()->GetElement("drake:ball_constraint");
         constraint_node;
         constraint_node = constraint_node->GetNextElement(
             "drake:ball_constraint")) {
      AddBallConstraintFromSpecification(diagnostic, constraint_node,
                                         model_instance, plant);
    }
  }

  if (model.Static()) {
    // Only weld / fixed joints are permissible.
    // TODO(eric.cousineau): Consider "freezing" non-weld joints, as is
    // permissible in Bullet and DART via Gazebo (#12227).
    for (sdf::JointType joint_type : joint_types) {
      if (joint_type != sdf::JointType::FIXED) {
        std::string message =
            "Only fixed joints are permitted in static models.";
        diagnostic.Error(model.Element(), std::move(message));
        return {};
      }
    }
    // Weld all links that have been added, but are not (yet) attached to the
    // world.
    // N.B. This implementation complicates "reposturing" a static model after
    // parsing. See #12227 and #14518 for more discussion.
    for (const LinkInfo& link_info : *added_link_infos) {
      if (!AreWelded(*plant, plant->world_body(), *link_info.body)) {
        const auto& A = plant->world_frame();
        const auto& B = link_info.body->body_frame();
        const std::string joint_name =
            "sdformat_model_static_" + A.name() + "_welds_to_" + B.name();
        plant->AddJoint(
            std::make_unique<WeldJoint<double>>(
                joint_name, A, B, link_info.X_WL));
      }
    }
  }

  // Parses the collision filter groups only if the scene graph is registered.
  if (plant->geometry_source_is_registered()) {
    drake::log()->trace("sdf_parser: Add collision filter groups");
    ParseCollisionFilterGroup(
        diagnostic, model_instance, model, plant, resolver);
  }

  return added_model_instances;
}

// Helper function that computes the default pose of a Frame
RigidTransformd GetDefaultFramePose(
    const MultibodyPlant<double>& plant,
    const Frame<double>& frame) {
  const RigidTransformd X_WB =
      plant.GetDefaultFreeBodyPose(frame.body());
  const RigidTransformd X_WF =
      X_WB * frame.GetFixedPoseInBodyFrame();
  return X_WF;
}

// For the libsdformat API, see:
// http://sdformat.org/tutorials?tut=composition_proposal
constexpr char kExtUrdf[] = ".urdf";
constexpr char kExtXml[] = ".xml";

void AddBodiesToInterfaceModel(const MultibodyPlant<double>& plant,
                               const sdf::InterfaceModelPtr& interface_model,
                               const std::vector<BodyIndex>& body_indices,
                               const RigidTransformd& X_MW) {
  for (auto index : body_indices) {
    const auto& link = plant.get_body(index);
    RigidTransformd X_ML = X_MW * plant.GetDefaultFreeBodyPose(link);
    interface_model->AddLink({link.name(), ToIgnitionPose3d(X_ML)});
  }
}

void AddFramesToInterfaceModel(const MultibodyPlant<double>& plant,
                               ModelInstanceIndex model_instance,
                               const sdf::InterfaceModelPtr& interface_model,
                               const std::vector<FrameIndex>& frame_indices,
                               const std::string& model_frame_name) {
  for (auto index : frame_indices) {
    const auto& frame = plant.get_frame(index);
    if (frame.model_instance() != model_instance) {
      continue;
    }
    if (frame.name().empty() || frame.name() == model_frame_name ||
        plant.HasBodyNamed(frame.name(), model_instance)) {
      // Skip unnamed frames, and __model__ since it's already added.  Also
      // skip frames with the same name as a link since those are frames added
      // by Drake and are considered implicit by SDFormat. Sending such frames
      // to SDFormat would imply that these frames are explicit (i.e., frames
      // created using the <frame> tag).
      continue;
    }
    interface_model->AddFrame(
        {frame.name(), GetRelativeBodyName(frame.body(), model_instance, plant),
         ToIgnitionPose3d(frame.GetFixedPoseInBodyFrame())});
  }
}

void AddJointsToInterfaceModel(const MultibodyPlant<double>& plant,
                               const sdf::InterfaceModelPtr& interface_model,
                               const std::vector<JointIndex>& joint_indices) {
  for (auto index : joint_indices) {
    const auto& joint = plant.get_joint(index);
    const std::string& child_name = joint.child_body().name();
    const RigidTransformd X_CJ =
        joint.frame_on_child().GetFixedPoseInBodyFrame();
    interface_model->AddJoint(
        {joint.name(), child_name, ToIgnitionPose3d(X_CJ)});
  }
}

// This is a forward-declaration of an anonymous helper that's defined later
// in this file.
sdf::ParserConfig MakeSdfParserConfig(const ParsingWorkspace&);

sdf::Error MakeSdfError(sdf::ErrorCode code, const DiagnosticDetail& detail) {
  sdf::Error result(code, detail.message);
  if (detail.filename.has_value()) {
    result.SetFilePath(*detail.filename);
  }
  if (detail.line.has_value()) {
    result.SetLineNumber(*detail.line);
  }
  return result;
}

ModelInstanceIndex GetOrCreateModelInstanceByName(
    MultibodyPlant<double>* plant, const std::string& model_name) {
  if (plant->HasModelInstanceNamed(model_name)) {
    return plant->GetModelInstanceByName(model_name);
  }
  return plant->AddModelInstance(model_name);
}

sdf::InterfaceModelPtr ConvertToInterfaceModel(
    MultibodyPlant<double>* plant, std::string model_name,
    const InterfaceModelHelper& interface_model_helper, sdf::Errors* errors,
    RigidTransformd X_WP = RigidTransformd::Identity()) {
  auto model_instance = interface_model_helper.model_instance();
  sdf::RepostureFunction reposture_model =
      [plant, model_instance,
       errors](const sdf::InterfaceModelPoseGraph& graph) {
        // N.B. This should also posture the model appropriately.
        for (auto interface_link_ind : plant->GetBodyIndices(model_instance)) {
          const auto& interface_link = plant->get_body(interface_link_ind);

          gz::math::Pose3d X_WL;
          sdf::Errors inner_errors =
              graph.ResolveNestedFramePose(X_WL, interface_link.name());
          PropagateErrors(std::move(inner_errors), errors);
          plant->SetDefaultFreeBodyPose(interface_link, ToRigidTransform(X_WL));
        }
      };

  const auto& model_frame = plant->GetFrameByName(
      interface_model_helper.model_frame_name(), model_instance);
  const std::string canonical_link_name =
      GetRelativeBodyName(model_frame.body(), model_instance, *plant);
  const RigidTransformd X_WM = GetDefaultFramePose(*plant, model_frame);
  const RigidTransformd X_PM = X_WP.inverse() * X_WM;

  auto interface_model = std::make_shared<sdf::InterfaceModel>(
      model_name, reposture_model, false, canonical_link_name,
      ToIgnitionPose3d(X_PM));

  AddBodiesToInterfaceModel(*plant, interface_model,
                            interface_model_helper.body_indices(),
                            X_WM.inverse());

  AddFramesToInterfaceModel(*plant, model_instance, interface_model,
                            interface_model_helper.frame_indices(),
                            interface_model_helper.model_frame_name());

  AddJointsToInterfaceModel(*plant, interface_model,
                            interface_model_helper.joint_indices());

  return interface_model;
}

// This assumes that parent models will have their parsing start before child
// models! This is a safe assumption because we only encounter nested models
// when force testing SDFormat files and libsdformat parses models in a top-down
// order. If we add support for other file formats, we should ensure that the
// parsers comply with this assumption.
sdf::InterfaceModelPtr ParseNestedInterfaceModel(
    const ParsingWorkspace& workspace,
    const sdf::NestedInclude& include, sdf::Errors* errors) {
  const sdf::ParserConfig parser_config = MakeSdfParserConfig(workspace);
  auto& [options, package_map, diagnostic, plant,
         collision_resolver, parser_selector] = workspace;
  const std::string resolved_filename{include.ResolvedFileName()};

  // Do not attempt to parse anything other than URDF and MuJoCo xml files.
  const bool is_urdf = EndsWithCaseInsensitive(resolved_filename, kExtUrdf);
  const bool is_xml = EndsWithCaseInsensitive(resolved_filename, kExtXml);
  if (!is_urdf && !is_xml) {
    return nullptr;
  }

  if (include.IsStatic()) {
    errors->emplace_back(
        sdf::ErrorCode::ELEMENT_INVALID,
        "Drake does not yet support //include/static for custom nesting.");
    return nullptr;
  }

  DataSource data_source(DataSource::kFilename, &resolved_filename);
  drake::internal::DiagnosticPolicy subdiagnostic;
  subdiagnostic.SetActionForWarnings(
      [&errors](const DiagnosticDetail& detail) {
        errors->emplace_back(MakeSdfError(
            sdf::ErrorCode::NONE, detail));
      });
  subdiagnostic.SetActionForErrors(
      [&errors](const DiagnosticDetail& detail) {
        errors->emplace_back(MakeSdfError(
            sdf::ErrorCode::ELEMENT_INVALID, detail));
      });

  ModelInstanceIndex main_model_instance;
  // New instances will have indices starting from cur_num_models
  const bool is_merge_include = include.IsMerge().value_or(false);

  InterfaceModelHelper interface_model_helper(*plant);
  ParsingWorkspace subworkspace{options, package_map, subdiagnostic, plant,
    collision_resolver, parser_selector};

  std::string model_frame_name = "__model__";
  std::string model_name;
  if (is_merge_include) {
    // Create the parent model instance if it hasn't been created already.
    // This can happen if this is the first model to be merge-included.
    const auto parent_model_instance =
      GetOrCreateModelInstanceByName(plant, include.AbsoluteParentName());

    interface_model_helper.TakeSnapShot(parent_model_instance);
    auto& parser = parser_selector(diagnostic, resolved_filename);
    model_name =
        parser.MergeModel(data_source, include.LocalModelName().value_or(""),
                          parent_model_instance, subworkspace);
    interface_model_helper.ComputeDiffFromSnapshot();

    model_frame_name = sdf::computeMergedModelProxyFrameName(model_name);
    interface_model_helper.set_model_frame_name(model_frame_name);

    main_model_instance = parent_model_instance;
  } else {
    const std::optional<ModelInstanceIndex> maybe_model =
        parser_selector(diagnostic, resolved_filename).
        AddModel(data_source, include.LocalModelName().value_or(""),
                include.AbsoluteParentName(), subworkspace);
    if (maybe_model.has_value()) {
      main_model_instance = *maybe_model;
    } else {
      return nullptr;
    }

    model_name =
        ScopedName::Parse(plant->GetModelInstanceName(main_model_instance))
            .get_element();

    // In the non-merge-include case, we don't need to compute the diff, but we
    // still use the interface_model_helper to hold all the frame bearing
    // elements of the newly created model instance so we can later convert
    // this model instance to an Interface Model for libsdformat's Interface
    // API.
    interface_model_helper.TakeSnapShot(main_model_instance);
  }
  DRAKE_DEMAND(!model_name.empty());

  // Add explicit model frame to first link.
  auto body_indices = workspace.plant->GetBodyIndices(main_model_instance);
  if (body_indices.empty()) {
    errors->emplace_back(sdf::ErrorCode::ELEMENT_INVALID,
                         "Model must have at least one link.");
    return nullptr;
  }
  const auto& canonical_link = workspace.plant->get_body(body_indices[0]);

  const Frame<double>& canonical_link_frame =
      plant->GetFrameByName(canonical_link.name(), main_model_instance);
  plant->AddFrame(
      std::make_unique<FixedOffsetFrame<double>>(
          model_frame_name, canonical_link_frame, RigidTransformd::Identity(),
          main_model_instance));

  // Now that the model is parsed, we create interface elements to send to
  // libsdformat.

  // This will be populated for the first model instance.
  sdf::InterfaceModelPtr main_interface_model;
  main_interface_model = ConvertToInterfaceModel(
      plant, model_name, interface_model_helper, errors);
  main_interface_model->SetParserSupportsMergeInclude(true);

  return main_interface_model;
}

// Note that this function keeps an alias of the input @p workspace in its
// return value. Therefore, the lifetime of the @p workspace must be greater
// than that of the returned parser config object.
sdf::ParserConfig MakeSdfParserConfig(const ParsingWorkspace& workspace) {
  // The error severity settings here are somewhat subtle. We set all of them
  // to ERR so that reports will append into an sdf::Errors collection instead
  // of spamming into spdlog. However, when grabbing the reports out of the
  // sdf::Errors collection later, we will categorize certain kinds of reports
  // (e.g., deprecations) back to a DiagnosticPolicy::Warning instead of Error.
  // That filter is specified in our IsError() helper, above.
  sdf::ParserConfig parser_config;
  parser_config.SetWarningsPolicy(sdf::EnforcementPolicy::ERR);
  parser_config.SetDeprecatedElementsPolicy(sdf::EnforcementPolicy::ERR);
  parser_config.SetUnrecognizedElementsPolicy(sdf::EnforcementPolicy::ERR);
  parser_config.SetFindCallback(
    [&workspace](const std::string &_input) {
      // This callback uses an empty return value to denote errors, and then its
      // caller reports its own "no such file" error directly. We'll route
      // Drake's specific messages about *why* the file wasn't found into a
      // debug-only log.
      DiagnosticPolicy debug_log;
      debug_log.SetActionForWarnings([](const DiagnosticDetail& detail) {
        drake::log()->debug(detail.FormatWarning());
      });
      debug_log.SetActionForErrors([](const DiagnosticDetail& detail) {
        drake::log()->debug(detail.FormatError());
      });
      return ResolveUri(debug_log, _input, workspace.package_map, ".");
    });

  parser_config.RegisterCustomModelParser(
      [&workspace](
          const sdf::NestedInclude& include, sdf::Errors& errors) {
        return ParseNestedInterfaceModel(workspace, include, &errors);
      });

  return parser_config;
}

const sdf::Model* get_only_model(const sdf::Root& root) {
  const sdf::Model* maybe_model = root.Model();
  if (maybe_model != nullptr) {
    return maybe_model;
  }
  // If Model() is null, there still may be a single world with a single model
  // (ignoring nested models). Try to find that.
  if (root.WorldCount() != 1) {
    return nullptr;
  }
  const sdf::World* world0 = root.WorldByIndex(0);
  if (world0->ModelCount() != 1) {
    return nullptr;
  }
  return world0->ModelByIndex(0);
}
}  // namespace

std::optional<ModelInstanceIndex> AddModelFromSdf(
    const DataSource& data_source, const std::string& model_name_in,
    const std::optional<std::string>& parent_model_name,
    const ParsingWorkspace& workspace) {
  DRAKE_THROW_UNLESS(!workspace.plant->is_finalized());

  sdf::ParserConfig parser_config = MakeSdfParserConfig(workspace);

  sdf::Root root;

  SDFormatDiagnostic diagnostic(&workspace.diagnostic, &data_source);

  const auto model_index_begin =
    static_cast<ModelInstanceIndex>(workspace.plant->num_model_instances());
  sdf::Errors errors = LoadSdf(
      diagnostic, &root, data_source, parser_config);
  if (diagnostic.PropagateErrors(errors)) {
    return std::nullopt;
  }
  const auto model_index_end =
      static_cast<ModelInstanceIndex>(workspace.plant->num_model_instances());

  ModelInstanceIndexRange reusable_model_instance_range =
      std::make_pair(model_index_begin, model_index_end);

  const sdf::Model* maybe_model = get_only_model(root);
  if (maybe_model == nullptr) {
    std::string message = "File must have a single <model> element.";
    diagnostic.Error(root.Element(), std::move(message));
    return std::nullopt;
  }
  // Get the only model in the file.
  const sdf::Model& model = *maybe_model;

  const std::string local_model_name =
      model_name_in.empty() ? model.Name() : model_name_in;

  std::string model_name =
      MakeModelName(local_model_name, parent_model_name, workspace);

  std::vector<ModelInstanceIndex> added_model_instances =
      AddModelsFromSpecification(
          diagnostic, model, model_name, {}, workspace.plant,
          workspace.collision_resolver, workspace.package_map,
          data_source.GetRootDir(), reusable_model_instance_range);

  DRAKE_DEMAND(!added_model_instances.empty());
  return added_model_instances.front();
}

std::vector<ModelInstanceIndex> AddModelsFromSdf(
    const DataSource& data_source,
    const std::optional<std::string>& parent_model_name,
    const ParsingWorkspace& workspace) {
  DRAKE_THROW_UNLESS(!workspace.plant->is_finalized());

  sdf::ParserConfig parser_config = MakeSdfParserConfig(workspace);

  sdf::Root root;

  SDFormatDiagnostic diagnostic(&workspace.diagnostic, &data_source);

  const auto model_index_begin =
    static_cast<ModelInstanceIndex>(workspace.plant->num_model_instances());
  sdf::Errors errors = LoadSdf(
      diagnostic, &root, data_source, parser_config);
  if (diagnostic.PropagateErrors(errors)) {
    return {};
  }

  const auto model_index_end =
      static_cast<ModelInstanceIndex>(workspace.plant->num_model_instances());

  // See comment on where the ModelInstanceIndexRange type is defined.
  ModelInstanceIndexRange reusable_model_instance_range =
      std::make_pair(model_index_begin, model_index_end);

  // There either must be exactly one model, or exactly one world.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  const uint64_t model_count = root.Model() != nullptr ? 1 : 0;
#pragma GCC diagnostic pop
  const uint64_t world_count = root.WorldCount();
  if ((model_count + world_count) != 1) {
    std::string message = fmt::format(
        "File must have exactly one <model> or exactly one <world>, but"
        " instead has {} models and {} worlds", model_count, world_count);
    diagnostic.Error(root.Element(), std::move(message));
    return {};
  }

  std::vector<ModelInstanceIndex> model_instances;

  // At this point there should be only Models or a single World at the Root
  // level.
  if (model_count > 0) {
    DRAKE_DEMAND(model_count == 1);
    DRAKE_DEMAND(world_count == 0);
    DRAKE_DEMAND(root.Model() != nullptr);
    const sdf::Model& model = *root.Model();

    std::string model_name =
        MakeModelName(model.Name(), parent_model_name, workspace);

    std::vector<ModelInstanceIndex> added_model_instances =
        AddModelsFromSpecification(
            diagnostic, model, model_name, {}, workspace.plant,
            workspace.collision_resolver, workspace.package_map,
            data_source.GetRootDir(), reusable_model_instance_range);
    model_instances.insert(model_instances.end(),
                           added_model_instances.begin(),
                           added_model_instances.end());
  } else {
    DRAKE_DEMAND(model_count == 0);
    DRAKE_DEMAND(world_count == 1);
    DRAKE_DEMAND(root.WorldByIndex(0) != nullptr);
    const sdf::World& world = *root.WorldByIndex(0);

    const std::set<std::string> supported_world_elements{
      "frame",
      "include",
      "joint",
      "model"};
    CheckSupportedElements(
        diagnostic, world.Element(), supported_world_elements);

    // TODO(eric.cousineau): Either support or explicitly prevent adding joints
    // via `//world/joint`, per this Bitbucket comment: https://bit.ly/2udQxhp

    for (uint64_t model_index = 0; model_index < world.ModelCount();
        ++model_index) {
      // Get the model.
      const sdf::Model& model = *world.ModelByIndex(model_index);
      std::string model_name =
          MakeModelName(model.Name(), parent_model_name, workspace);

      std::vector<ModelInstanceIndex> added_model_instances =
          AddModelsFromSpecification(
              diagnostic, model, model_name, {}, workspace.plant,
              workspace.collision_resolver, workspace.package_map,
              data_source.GetRootDir(), reusable_model_instance_range);
      model_instances.insert(model_instances.end(),
                             added_model_instances.begin(),
                             added_model_instances.end());
    }

    for (uint64_t frame_index = 0; frame_index < world.FrameCount();
        ++frame_index) {
      const sdf::Frame& frame = *world.FrameByIndex(frame_index);
      AddFrameFromSpecification(
          diagnostic, frame, world_model_instance(),
          workspace.plant->world_frame(), workspace.plant);
    }

    // Add all the joints
    std::set<sdf::JointType> joint_types;
    for (uint64_t joint_index = 0; joint_index < world.JointCount();
        ++joint_index) {
      const sdf::Joint& joint = *world.JointByIndex(joint_index);
      if (!AddJointFromSpecification(
          diagnostic, {}, joint, world_model_instance(),
          workspace.plant, &joint_types, false)) {
          return {};
        }
    }

    // Parse drake:mimic elements only after all joints have been added.
    for (uint64_t joint_index = 0; joint_index < world.JointCount();
         ++joint_index) {
      // Get a pointer to the SDF joint, and the joint axis information.
      const sdf::Joint& joint = *world.JointByIndex(joint_index);
      if (!ParseMimicTag(diagnostic, joint, world_model_instance(),
                         workspace.plant)) {
        return {};
      }
    }

    for (sdf::JointType joint_type : joint_types) {
      if (joint_type != sdf::JointType::FIXED) {
        std::string message =
            "Only fixed joints are permitted in world joints.";
        diagnostic.Error(world.Element(), std::move(message));
        return {};
      }
    }
  }

  return model_instances;
}

SdfParserWrapper::SdfParserWrapper() {}

SdfParserWrapper::~SdfParserWrapper() {}

std::optional<ModelInstanceIndex> SdfParserWrapper::AddModel(
    const DataSource& data_source, const std::string& model_name,
    const std::optional<std::string>& parent_model_name,
    const ParsingWorkspace& workspace) {
  return AddModelFromSdf(data_source, model_name, parent_model_name, workspace);
}

std::vector<ModelInstanceIndex> SdfParserWrapper::AddAllModels(
    const DataSource& data_source,
    const std::optional<std::string>& parent_model_name,
    const ParsingWorkspace& workspace) {
  return AddModelsFromSdf(data_source, parent_model_name, workspace);
}

}  // namespace internal
}  // namespace multibody
}  // namespace drake
